package org.apache.http.impl.client;

import java.io.Closeable;
import java.net.ProxySelector;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;

import org.apache.http.ConnectionReuseStrategy;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.auth.AuthSchemeProvider;
import org.apache.http.client.AuthenticationStrategy;
import org.apache.http.client.BackoffManager;
import org.apache.http.client.ConnectionBackoffStrategy;
import org.apache.http.client.CookieStore;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.RedirectStrategy;
import org.apache.http.client.ServiceUnavailableRetryStrategy;
import org.apache.http.client.UserTokenHandler;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.protocol.RequestAcceptEncoding;
import org.apache.http.client.protocol.RequestAddCookies;
import org.apache.http.client.protocol.RequestAuthCache;
import org.apache.http.client.protocol.RequestClientConnControl;
import org.apache.http.client.protocol.RequestDefaultHeaders;
import org.apache.http.client.protocol.RequestExpectContinue;
import org.apache.http.client.protocol.ResponseContentEncoding;
import org.apache.http.client.protocol.ResponseProcessCookies;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.Lookup;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.SchemePortResolver;
import org.apache.http.conn.routing.HttpRoutePlanner;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.cookie.CookieSpecProvider;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.NoConnectionReuseStrategy;
import org.apache.http.impl.auth.BasicSchemeFactory;
import org.apache.http.impl.auth.DigestSchemeFactory;
import org.apache.http.impl.auth.KerberosSchemeFactory;
import org.apache.http.impl.auth.NTLMSchemeFactory;
import org.apache.http.impl.auth.SPNegoSchemeFactory;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.DefaultRedirectStrategy;
import org.apache.http.impl.client.DefaultUserTokenHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.NoopUserTokenHandler;
import org.apache.http.impl.client.ProxyAuthenticationStrategy;
import org.apache.http.impl.client.SystemDefaultCredentialsProvider;
import org.apache.http.impl.client.TargetAuthenticationStrategy;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.apache.http.impl.conn.DefaultRoutePlanner;
import org.apache.http.impl.conn.DefaultSchemePortResolver;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.conn.SystemDefaultRoutePlanner;
import org.apache.http.impl.cookie.BestMatchSpecFactory;
import org.apache.http.impl.cookie.BrowserCompatSpecFactory;
import org.apache.http.impl.cookie.IgnoreSpecFactory;
import org.apache.http.impl.cookie.NetscapeDraftSpecFactory;
import org.apache.http.impl.cookie.RFC2109SpecFactory;
import org.apache.http.impl.cookie.RFC2965SpecFactory;
import org.apache.http.impl.execchain.BackoffStrategyExec;
import org.apache.http.impl.execchain.ClientExecChain;
import org.apache.http.impl.execchain.MainClientExec;
import org.apache.http.impl.execchain.ProtocolExec;
import org.apache.http.impl.execchain.RetryExec;
import org.apache.http.impl.execchain.ServiceUnavailableRetryExec;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.HttpProcessorBuilder;
import org.apache.http.protocol.HttpRequestExecutor;
import org.apache.http.protocol.RequestContent;
import org.apache.http.protocol.RequestTargetHost;
import org.apache.http.protocol.RequestUserAgent;
import org.apache.http.util.TextUtils;
import org.apache.http.util.VersionInfo;

import com.xiaotu.spider.downloader.MyRedirectExec;

/**
 * @类名 MyHttpClientBuilder
 * @日期 2017年3月21日
 * @作者 高海军
 * @功能
 */
public class MyHttpClientBuilder extends HttpClientBuilder
{
	
	private HttpRequestExecutor requestExec;
	private X509HostnameVerifier hostnameVerifier;
	private LayeredConnectionSocketFactory sslSocketFactory;
	private SSLContext sslcontext;
	private HttpClientConnectionManager connManager;
	private SchemePortResolver schemePortResolver;
	private ConnectionReuseStrategy reuseStrategy;
	private ConnectionKeepAliveStrategy keepAliveStrategy;
	private AuthenticationStrategy targetAuthStrategy;
	private AuthenticationStrategy proxyAuthStrategy;
	private UserTokenHandler userTokenHandler;
	private HttpProcessor httpprocessor;
	
	private LinkedList<HttpRequestInterceptor> requestFirst;
	private LinkedList<HttpRequestInterceptor> requestLast;
	private LinkedList<HttpResponseInterceptor> responseFirst;
	private LinkedList<HttpResponseInterceptor> responseLast;
	
	private HttpRequestRetryHandler retryHandler;
	private HttpRoutePlanner routePlanner;
	private RedirectStrategy redirectStrategy;
	private ConnectionBackoffStrategy connectionBackoffStrategy;
	private BackoffManager backoffManager;
	private ServiceUnavailableRetryStrategy serviceUnavailStrategy;
	private Lookup<AuthSchemeProvider> authSchemeRegistry;
	private Lookup<CookieSpecProvider> cookieSpecRegistry;
	private CookieStore cookieStore;
	private CredentialsProvider credentialsProvider;
	private String userAgent;
	private HttpHost proxy;
	private Collection<? extends Header> defaultHeaders;
	private SocketConfig defaultSocketConfig;
	private ConnectionConfig defaultConnectionConfig;
	private RequestConfig defaultRequestConfig;
	
	private boolean systemProperties;
	private boolean redirectHandlingDisabled;
	private boolean automaticRetriesDisabled;
	private boolean contentCompressionDisabled;
	private boolean cookieManagementDisabled;
	private boolean authCachingDisabled;
	private boolean connectionStateDisabled;
	
	private int maxConnTotal = 0;
	private int maxConnPerRoute = 0;
	
	private List<Closeable> closeables;
	
	static final String DEFAULT_USER_AGENT;
	static
	{
		final VersionInfo vi = VersionInfo.loadVersionInfo(
				"org.apache.http.client",
				HttpClientBuilder.class.getClassLoader());
		final String release = (vi != null) ? vi.getRelease()
				: VersionInfo.UNAVAILABLE;
		DEFAULT_USER_AGENT = "Apache-HttpClient/" + release + " (java 1.5)";
	}
	
	public static HttpClientBuilder create()
	{
		return new MyHttpClientBuilder();
	}
	
	private static String[] split(final String s)
	{
		if (TextUtils.isBlank(s))
		{
			return null;
		}
		return s.split(" *, *");
	}
	
	public CloseableHttpClient build()
	{
		// Create main request executor
		HttpRequestExecutor requestExec = this.requestExec;
		if (requestExec == null)
		{
			requestExec = new HttpRequestExecutor();
		}
		HttpClientConnectionManager connManager = this.connManager;
		if (connManager == null)
		{
			LayeredConnectionSocketFactory sslSocketFactory = this.sslSocketFactory;
			if (sslSocketFactory == null)
			{
				final String[] supportedProtocols = systemProperties
						? split(System.getProperty("https.protocols")) : null;
				final String[] supportedCipherSuites = systemProperties
						? split(System.getProperty("https.cipherSuites"))
						: null;
				X509HostnameVerifier hostnameVerifier = this.hostnameVerifier;
				if (hostnameVerifier == null)
				{
					hostnameVerifier = SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER;
				}
				if (sslcontext != null)
				{
					sslSocketFactory = new SSLConnectionSocketFactory(
							sslcontext, supportedProtocols,
							supportedCipherSuites, hostnameVerifier);
				}
				else
				{
					if (systemProperties)
					{
						sslSocketFactory = new SSLConnectionSocketFactory(
								(SSLSocketFactory) SSLSocketFactory
										.getDefault(),
								supportedProtocols, supportedCipherSuites,
								hostnameVerifier);
					}
					else
					{
						sslSocketFactory = new SSLConnectionSocketFactory(
								SSLContexts.createDefault(), hostnameVerifier);
					}
				}
			}
			@SuppressWarnings("resource")
			final PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager(
					RegistryBuilder.<ConnectionSocketFactory> create()
							.register("http",
									PlainConnectionSocketFactory
											.getSocketFactory())
							.register("https", sslSocketFactory).build());
			if (defaultSocketConfig != null)
			{
				poolingmgr.setDefaultSocketConfig(defaultSocketConfig);
			}
			if (defaultConnectionConfig != null)
			{
				poolingmgr.setDefaultConnectionConfig(defaultConnectionConfig);
			}
			if (systemProperties)
			{
				String s = System.getProperty("http.keepAlive", "true");
				if ("true".equalsIgnoreCase(s))
				{
					s = System.getProperty("http.maxConnections", "5");
					final int max = Integer.parseInt(s);
					poolingmgr.setDefaultMaxPerRoute(max);
					poolingmgr.setMaxTotal(2 * max);
				}
			}
			if (maxConnTotal > 0)
			{
				poolingmgr.setMaxTotal(maxConnTotal);
			}
			if (maxConnPerRoute > 0)
			{
				poolingmgr.setDefaultMaxPerRoute(maxConnPerRoute);
			}
			connManager = poolingmgr;
		}
		ConnectionReuseStrategy reuseStrategy = this.reuseStrategy;
		if (reuseStrategy == null)
		{
			if (systemProperties)
			{
				final String s = System.getProperty("http.keepAlive", "true");
				if ("true".equalsIgnoreCase(s))
				{
					reuseStrategy = DefaultConnectionReuseStrategy.INSTANCE;
				}
				else
				{
					reuseStrategy = NoConnectionReuseStrategy.INSTANCE;
				}
			}
			else
			{
				reuseStrategy = DefaultConnectionReuseStrategy.INSTANCE;
			}
		}
		ConnectionKeepAliveStrategy keepAliveStrategy = this.keepAliveStrategy;
		if (keepAliveStrategy == null)
		{
			keepAliveStrategy = DefaultConnectionKeepAliveStrategy.INSTANCE;
		}
		AuthenticationStrategy targetAuthStrategy = this.targetAuthStrategy;
		if (targetAuthStrategy == null)
		{
			targetAuthStrategy = TargetAuthenticationStrategy.INSTANCE;
		}
		AuthenticationStrategy proxyAuthStrategy = this.proxyAuthStrategy;
		if (proxyAuthStrategy == null)
		{
			proxyAuthStrategy = ProxyAuthenticationStrategy.INSTANCE;
		}
		UserTokenHandler userTokenHandler = this.userTokenHandler;
		if (userTokenHandler == null)
		{
			if (!connectionStateDisabled)
			{
				userTokenHandler = DefaultUserTokenHandler.INSTANCE;
			}
			else
			{
				userTokenHandler = NoopUserTokenHandler.INSTANCE;
			}
		}
		ClientExecChain execChain = new MainClientExec(requestExec, connManager,
				reuseStrategy, keepAliveStrategy, targetAuthStrategy,
				proxyAuthStrategy, userTokenHandler);
		
		execChain = decorateMainExec(execChain);
		
		HttpProcessor httpprocessor = this.httpprocessor;
		if (httpprocessor == null)
		{
			
			String userAgent = this.userAgent;
			if (userAgent == null)
			{
				if (systemProperties)
				{
					userAgent = System.getProperty("http.agent");
				}
				if (userAgent == null)
				{
					userAgent = DEFAULT_USER_AGENT;
				}
			}
			
			final HttpProcessorBuilder b = HttpProcessorBuilder.create();
			if (requestFirst != null)
			{
				for (final HttpRequestInterceptor i : requestFirst)
				{
					b.addFirst(i);
				}
			}
			if (responseFirst != null)
			{
				for (final HttpResponseInterceptor i : responseFirst)
				{
					b.addFirst(i);
				}
			}
			b.addAll(new RequestDefaultHeaders(defaultHeaders),
					new RequestContent(), new RequestTargetHost(),
					new RequestClientConnControl(),
					new RequestUserAgent(userAgent),
					new RequestExpectContinue());
			if (!cookieManagementDisabled)
			{
				b.add(new RequestAddCookies());
			}
			if (!contentCompressionDisabled)
			{
				b.add(new RequestAcceptEncoding());
			}
			if (!authCachingDisabled)
			{
				b.add(new RequestAuthCache());
			}
			if (!cookieManagementDisabled)
			{
				b.add(new ResponseProcessCookies());
			}
			if (!contentCompressionDisabled)
			{
				b.add(new ResponseContentEncoding());
			}
			if (requestLast != null)
			{
				for (final HttpRequestInterceptor i : requestLast)
				{
					b.addLast(i);
				}
			}
			if (responseLast != null)
			{
				for (final HttpResponseInterceptor i : responseLast)
				{
					b.addLast(i);
				}
			}
			httpprocessor = b.build();
		}
		execChain = new ProtocolExec(execChain, httpprocessor);
		
		execChain = decorateProtocolExec(execChain);
		
		// Add request retry executor, if not disabled
		if (!automaticRetriesDisabled)
		{
			HttpRequestRetryHandler retryHandler = this.retryHandler;
			if (retryHandler == null)
			{
				retryHandler = DefaultHttpRequestRetryHandler.INSTANCE;
			}
			execChain = new RetryExec(execChain, retryHandler);
		}
		
		HttpRoutePlanner routePlanner = this.routePlanner;
		if (routePlanner == null)
		{
			SchemePortResolver schemePortResolver = this.schemePortResolver;
			if (schemePortResolver == null)
			{
				schemePortResolver = DefaultSchemePortResolver.INSTANCE;
			}
			if (proxy != null)
			{
				routePlanner = new DefaultProxyRoutePlanner(proxy,
						schemePortResolver);
			}
			else if (systemProperties)
			{
				routePlanner = new SystemDefaultRoutePlanner(schemePortResolver,
						ProxySelector.getDefault());
			}
			else
			{
				routePlanner = new DefaultRoutePlanner(schemePortResolver);
			}
		}
		// Add redirect executor, if not disabled
		if (!redirectHandlingDisabled)
		{
			RedirectStrategy redirectStrategy = this.redirectStrategy;
			if (redirectStrategy == null)
			{
				redirectStrategy = DefaultRedirectStrategy.INSTANCE;
			}
			execChain = new MyRedirectExec(execChain, routePlanner,
					redirectStrategy);
		}
		
		// Optionally, add service unavailable retry executor
		final ServiceUnavailableRetryStrategy serviceUnavailStrategy = this.serviceUnavailStrategy;
		if (serviceUnavailStrategy != null)
		{
			execChain = new ServiceUnavailableRetryExec(execChain,
					serviceUnavailStrategy);
		}
		// Optionally, add connection back-off executor
		final BackoffManager backoffManager = this.backoffManager;
		final ConnectionBackoffStrategy connectionBackoffStrategy = this.connectionBackoffStrategy;
		if (backoffManager != null && connectionBackoffStrategy != null)
		{
			execChain = new BackoffStrategyExec(execChain,
					connectionBackoffStrategy, backoffManager);
		}
		
		Lookup<AuthSchemeProvider> authSchemeRegistry = this.authSchemeRegistry;
		if (authSchemeRegistry == null)
		{
			authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider> create()
					.register(AuthSchemes.BASIC, new BasicSchemeFactory())
					.register(AuthSchemes.DIGEST, new DigestSchemeFactory())
					.register(AuthSchemes.NTLM, new NTLMSchemeFactory())
					.register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory())
					.register(AuthSchemes.KERBEROS, new KerberosSchemeFactory())
					.build();
		}
		Lookup<CookieSpecProvider> cookieSpecRegistry = this.cookieSpecRegistry;
		if (cookieSpecRegistry == null)
		{
			cookieSpecRegistry = RegistryBuilder.<CookieSpecProvider> create()
					.register(CookieSpecs.BEST_MATCH,
							new BestMatchSpecFactory())
					.register(CookieSpecs.STANDARD, new RFC2965SpecFactory())
					.register(CookieSpecs.BROWSER_COMPATIBILITY,
							new BrowserCompatSpecFactory())
					.register(CookieSpecs.NETSCAPE,
							new NetscapeDraftSpecFactory())
					.register(CookieSpecs.IGNORE_COOKIES,
							new IgnoreSpecFactory())
					.register("rfc2109", new RFC2109SpecFactory())
					.register("rfc2965", new RFC2965SpecFactory()).build();
		}
		
		CookieStore defaultCookieStore = this.cookieStore;
		if (defaultCookieStore == null)
		{
			defaultCookieStore = new BasicCookieStore();
		}
		
		CredentialsProvider defaultCredentialsProvider = this.credentialsProvider;
		if (defaultCredentialsProvider == null)
		{
			if (systemProperties)
			{
				defaultCredentialsProvider = new SystemDefaultCredentialsProvider();
			}
			else
			{
				defaultCredentialsProvider = new BasicCredentialsProvider();
			}
		}
		
		return new InternalHttpClient(execChain, connManager, routePlanner,
				cookieSpecRegistry, authSchemeRegistry, defaultCookieStore,
				defaultCredentialsProvider,
				defaultRequestConfig != null ? defaultRequestConfig
						: RequestConfig.DEFAULT,
				closeables != null ? new ArrayList<Closeable>(closeables)
						: null);
	}
	
}
